package com.huahua.api.service.impl;

import com.huahua.api.entity.param.clipboard.*;
import com.huahua.api.entity.pojo.WebClipboard;
import com.huahua.api.entity.vo.clipboard.RandomPasswordVO;
import com.huahua.api.entity.vo.clipboard.WebClipboardVO;
import com.huahua.api.exception.CommonException;
import com.huahua.api.repository.WebClipboardRepository;
import com.huahua.api.service.WebClipboardService;
import com.huahua.api.util.CheckUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.RandomUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDateTime;

/**
 * @author Huahua-Ai
 * @date 2025-01-21 22:25:00
 * @description 网络剪贴板服务实现
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class WebClipboardServiceImpl implements WebClipboardService {

    private final WebClipboardRepository webClipboardRepository;

    private static final int MAX_RETRY_COUNT = 10;
    private static final int SHARE_ID_LENGTH = 8;
    private static final String SHARE_ID_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static final int PASSWORD_LENGTH = 6;
    private static final int ADMIN_PASSWORD_LENGTH = 8;
    private static final int DEFAULT_EXPIRE_HOURS = 24; // 默认24小时

    @Override
    public Mono<WebClipboardVO> createClipboard(CreateClipboardParam param) {
        return Mono.fromCallable(() -> {
                    CheckUtil.checkStrNotBlank(param.getContent(), new CommonException("剪贴板内容不能为空"));

                    // 如果提供了过期时间，检查是否晚于当前时间
                    LocalDateTime now = LocalDateTime.now();
                    if (param.getExpireTime() != null) {
                        CheckUtil.checkTrue(param.getExpireTime().isAfter(now),
                                new CommonException("过期时间必须晚于当前时间"));
                    }

                    if (param.getMaxAccessCount() != null) {
                        CheckUtil.checkTrue(param.getMaxAccessCount() > 0,
                                new CommonException("最大访问次数必须大于0"));
                    }
                    return param;
                })
                // 生成分享ID
                .flatMap(p -> generateUniqueShareId())
                .flatMap(shareId -> {
                    LocalDateTime now = LocalDateTime.now();
                    WebClipboard clipboard = new WebClipboard();
                    clipboard.setShareId(shareId);

                    // 设置访问密码（留空表示不设置访问密码）
                    clipboard.setPassword(param.getPassword());

                    // 生成管理员密码（始终生成）
                    String adminPassword = generateAdminPassword();
                    clipboard.setAdminPassword(adminPassword);

                    clipboard.setContent(param.getContent());
                    clipboard.setIsEditable(param.getIsEditable());

                    // 设置过期时间，如果未提供则默认24小时后
                    if (param.getExpireTime() != null) {
                        clipboard.setExpireTime(param.getExpireTime());
                    } else {
                        clipboard.setExpireTime(now.plusHours(DEFAULT_EXPIRE_HOURS));
                    }

                    clipboard.setCreatedTime(now);
                    clipboard.setUpdatedTime(now);
                    clipboard.setAccessCount(0);
                    clipboard.setMaxAccessCount(param.getMaxAccessCount());

                    return webClipboardRepository.save(clipboard);
                })
                .map(clipboard -> {
                    log.info("创建剪贴板保存后: shareId={}, password=[{}], hasPasswordCheck={}",
                            clipboard.getShareId(),
                            clipboard.getPassword(),
                            StrUtil.isNotBlank(clipboard.getPassword()));
                    WebClipboardVO vo = convertToVO(clipboard);
                    // 创建时返回管理员密码
                    vo.setAdminPassword(clipboard.getAdminPassword());
                    log.info("转换VO后: hasPassword={}", vo.getHasPassword());
                    return vo;
                })
                .doOnSuccess(vo -> log.info("创建剪贴板成功: shareId={}", vo.getShareId()))
                .onErrorResume(e -> {
                    log.error("创建剪贴板失败: {}", e.getMessage());
                    return Mono.error(e);
                });
    }

    @Override
    public Mono<WebClipboardVO> getClipboard(GetClipboardParam param) {
        CheckUtil.checkStrNotBlank(param.getShareId(), new CommonException("分享ID不能为空"));

        return webClipboardRepository.findByShareId(param.getShareId())
                .switchIfEmpty(Mono.error(new CommonException("剪贴板不存在或已过期")))
                .flatMap(clipboard -> {
                    // 检查是否过期
                    if (clipboard.getExpireTime().isBefore(LocalDateTime.now())) {
                        return webClipboardRepository.delete(clipboard)
                                .then(Mono.error(new CommonException("剪贴板已过期")));
                    }

                    // 验证密码并判断是否为管理员访问
                    boolean isValidPassword = false;
                    boolean isAdminAccess = false;

                    if (StrUtil.isNotBlank(param.getPassword())) {
                        // 检查是否为管理员密码
                        if (param.getPassword().equals(clipboard.getAdminPassword())) {
                            isValidPassword = true;
                            isAdminAccess = true;
                        }
                        // 检查是否为访问密码
                        else if (StrUtil.isNotBlank(clipboard.getPassword()) &&
                                param.getPassword().equals(clipboard.getPassword())) {
                            isValidPassword = true;
                            isAdminAccess = false;
                        }
                    } else if (StrUtil.isBlank(clipboard.getPassword())) {
                        // 没有设置访问密码，可以直接访问
                        isValidPassword = true;
                        isAdminAccess = false;
                    }

                    if (!isValidPassword) {
                        return Mono.error(new CommonException("密码错误或需要密码"));
                    }

                    // 检查访问次数限制
                    if (clipboard.getMaxAccessCount() != null &&
                            clipboard.getAccessCount() >= clipboard.getMaxAccessCount()) {
                        return Mono.error(new CommonException("剪贴板访问次数已达上限"));
                    }

                    // 更新访问信息并携带管理员标识
                    final boolean finalIsAdminAccess = isAdminAccess;
                    return webClipboardRepository.updateAccessInfo(param.getShareId(), LocalDateTime.now())
                            .then(Mono.just(new Object[]{clipboard, finalIsAdminAccess}));
                })
                .map(result -> {
                    Object[] data = result;
                    WebClipboard clipboard = (WebClipboard) data[0];
                    boolean isAdminAccess = (Boolean) data[1];
                    return convertToVO(clipboard, isAdminAccess);
                });
    }

    @Override
    public Mono<WebClipboardVO> updateClipboard(UpdateClipboardParam param) {
        CheckUtil.checkStrNotBlank(param.getShareId(), new CommonException("分享ID不能为空"));
        CheckUtil.checkStrNotBlank(param.getContent(), new CommonException("剪贴板内容不能为空"));

        return webClipboardRepository.findByShareId(param.getShareId())
                .switchIfEmpty(Mono.error(new CommonException("剪贴板不存在")))
                .flatMap(clipboard -> {
                    // 检查是否过期
                    if (clipboard.getExpireTime().isBefore(LocalDateTime.now())) {
                        return Mono.error(new CommonException("剪贴板已过期"));
                    }

                    // 验证密码权限
                    boolean isAdmin = StrUtil.isNotBlank(param.getPassword()) &&
                            param.getPassword().equals(clipboard.getAdminPassword());

                    // 检查是否为管理员密码

                    // 如果是管理员，直接允许编辑
                    if (!isAdmin) {
                        // 非管理员，检查是否可编辑
                        if (!clipboard.getIsEditable()) {
                            return Mono.error(new CommonException("该剪贴板不允许编辑"));
                        }

                        // 验证访问密码
                        boolean isValidPassword = false;
                        if (StrUtil.isNotBlank(param.getPassword())) {
                            // 检查是否为访问密码
                            if (StrUtil.isNotBlank(clipboard.getPassword()) &&
                                    param.getPassword().equals(clipboard.getPassword())) {
                                isValidPassword = true;
                            }
                        } else if (StrUtil.isBlank(clipboard.getPassword())) {
                            // 没有设置访问密码，可以直接编辑
                            isValidPassword = true;
                        }

                        if (!isValidPassword) {
                            return Mono.error(new CommonException("密码错误或需要密码"));
                        }
                    }

                    // 更新内容
                    clipboard.setContent(param.getContent());
                    clipboard.setUpdatedTime(LocalDateTime.now());

                    return webClipboardRepository.save(clipboard);
                })
                .map(this::convertToVO);
    }

    @Override
    public Mono<Boolean> changeAccessPassword(ChangeAccessPasswordParam param) {
        CheckUtil.checkStrNotBlank(param.getShareId(), new CommonException("分享ID不能为空"));

        return webClipboardRepository.findByShareId(param.getShareId())
                .switchIfEmpty(Mono.error(new CommonException("剪贴板不存在")))
                .flatMap(clipboard -> {
                    // 检查是否过期
                    if (clipboard.getExpireTime().isBefore(LocalDateTime.now())) {
                        return Mono.error(new CommonException("剪贴板已过期"));
                    }

                    // 验证管理员密码（修改访问密码需要管理员权限）
                    CheckUtil.checkStrNotBlank(param.getAdminPassword(), new CommonException("请输入管理员密码"));
                    CheckUtil.checkTrue(clipboard.getAdminPassword().equals(param.getAdminPassword()),
                            new CommonException("管理员密码错误"));

                    // 更新访问密码（可以为空，表示移除访问密码）
                    return webClipboardRepository.updatePassword(
                            param.getShareId(),
                            param.getNewAccessPassword(),
                            LocalDateTime.now()
                    );
                })
                .map(count -> count > 0)
                .doOnSuccess(success -> {
                    if (success) {
                        log.info("修改访问密码成功: shareId={}", param.getShareId());
                    }
                });
    }

    @Override
    public Mono<Boolean> changeAdminPassword(ChangeAdminPasswordParam param) {
        CheckUtil.checkStrNotBlank(param.getShareId(), new CommonException("分享ID不能为空"));
        CheckUtil.checkStrNotBlank(param.getNewAdminPassword(), new CommonException("新管理员密码不能为空"));

        return webClipboardRepository.findByShareId(param.getShareId())
                .switchIfEmpty(Mono.error(new CommonException("剪贴板不存在")))
                .flatMap(clipboard -> {
                    // 检查是否过期
                    if (clipboard.getExpireTime().isBefore(LocalDateTime.now())) {
                        return Mono.error(new CommonException("剪贴板已过期"));
                    }

                    // 验证原管理员密码
                    CheckUtil.checkStrNotBlank(param.getOldAdminPassword(), new CommonException("请输入原管理员密码"));
                    CheckUtil.checkTrue(clipboard.getAdminPassword().equals(param.getOldAdminPassword()),
                            new CommonException("原管理员密码错误"));

                    // 更新管理员密码
                    return webClipboardRepository.updateAdminPassword(
                            param.getShareId(),
                            param.getNewAdminPassword(),
                            LocalDateTime.now()
                    );
                })
                .map(count -> count > 0)
                .doOnSuccess(success -> {
                    if (success) {
                        log.info("修改管理员密码成功: shareId={}", param.getShareId());
                    }
                });
    }

    @Override
    public Mono<RandomPasswordVO> generateRandomAccessPassword(GenerateRandomAccessPasswordParam param) {
        CheckUtil.checkStrNotBlank(param.getShareId(), new CommonException("分享ID不能为空"));

        return webClipboardRepository.findByShareId(param.getShareId())
                .switchIfEmpty(Mono.error(new CommonException("剪贴板不存在")))
                .flatMap(clipboard -> {
                    // 检查是否过期
                    if (clipboard.getExpireTime().isBefore(LocalDateTime.now())) {
                        return Mono.error(new CommonException("剪贴板已过期"));
                    }

                    // 验证管理员密码（生成新访问密码需要管理员权限）
                    CheckUtil.checkStrNotBlank(param.getAdminPassword(), new CommonException("请输入管理员密码"));
                    CheckUtil.checkTrue(clipboard.getAdminPassword().equals(param.getAdminPassword()),
                            new CommonException("管理员密码错误"));

                    // 生成新的随机访问密码
                    String newAccessPassword = generateDefaultPassword();

                    // 更新访问密码
                    return webClipboardRepository.updatePassword(
                            param.getShareId(),
                            newAccessPassword,
                            LocalDateTime.now()
                    ).then(Mono.just(new RandomPasswordVO(param.getShareId(), newAccessPassword)));
                })
                .doOnSuccess(vo -> log.info("生成随机访问密码成功: shareId={}", vo.getShareId()));
    }

    @Override
    public Mono<RandomPasswordVO> generateRandomAdminPassword(GenerateRandomAdminPasswordParam param) {
        CheckUtil.checkStrNotBlank(param.getShareId(), new CommonException("分享ID不能为空"));

        return webClipboardRepository.findByShareId(param.getShareId())
                .switchIfEmpty(Mono.error(new CommonException("剪贴板不存在")))
                .flatMap(clipboard -> {
                    // 检查是否过期
                    if (clipboard.getExpireTime().isBefore(LocalDateTime.now())) {
                        return Mono.error(new CommonException("剪贴板已过期"));
                    }

                    // 验证原管理员密码
                    CheckUtil.checkStrNotBlank(param.getOldAdminPassword(), new CommonException("请输入原管理员密码"));
                    CheckUtil.checkTrue(clipboard.getAdminPassword().equals(param.getOldAdminPassword()),
                            new CommonException("原管理员密码错误"));

                    // 生成新的随机管理员密码
                    String newAdminPassword = generateAdminPassword();

                    // 更新管理员密码
                    return webClipboardRepository.updateAdminPassword(
                            param.getShareId(),
                            newAdminPassword,
                            LocalDateTime.now()
                    ).then(Mono.just(new RandomPasswordVO(param.getShareId(), newAdminPassword)));
                })
                .doOnSuccess(vo -> log.info("生成随机管理员密码成功: shareId={}", vo.getShareId()));
    }

    @Override
    public Mono<WebClipboardVO> setEditable(SetEditableParam param) {
        CheckUtil.checkStrNotBlank(param.getShareId(), new CommonException("分享ID不能为空"));
        CheckUtil.checkNotNull(param.getIsEditable(), new CommonException("编辑权限设置不能为空"));

        return webClipboardRepository.findByShareId(param.getShareId())
                .switchIfEmpty(Mono.error(new CommonException("剪贴板不存在")))
                .flatMap(clipboard -> {
                    // 检查是否过期
                    if (clipboard.getExpireTime().isBefore(LocalDateTime.now())) {
                        return Mono.error(new CommonException("剪贴板已过期"));
                    }

                    // 验证管理员密码（设置编辑权限需要管理员权限）
                    CheckUtil.checkStrNotBlank(param.getAdminPassword(), new CommonException("请输入管理员密码"));
                    CheckUtil.checkTrue(clipboard.getAdminPassword().equals(param.getAdminPassword()),
                            new CommonException("管理员密码错误"));

                    // 更新编辑权限
                    clipboard.setIsEditable(param.getIsEditable());
                    clipboard.setUpdatedTime(LocalDateTime.now());

                    return webClipboardRepository.save(clipboard);
                })
                .map(this::convertToVO)
                .doOnSuccess(vo -> log.info("设置编辑权限成功: shareId={}, isEditable={}",
                        param.getShareId(), param.getIsEditable()));
    }

    @Override
    public Mono<WebClipboardVO> setExpireTime(ExtendExpireParam param) {
        CheckUtil.checkStrNotBlank(param.getShareId(), new CommonException("分享ID不能为空"));
        CheckUtil.checkNotNull(param.getExpireTime(), new CommonException("过期时间不能为空"));

        LocalDateTime now = LocalDateTime.now();
        CheckUtil.checkTrue(param.getExpireTime().isAfter(now),
                new CommonException("过期时间必须晚于当前时间"));

        return webClipboardRepository.findByShareId(param.getShareId())
                .switchIfEmpty(Mono.error(new CommonException("剪贴板不存在")))
                .flatMap(clipboard -> {
                    // 验证管理员密码（修改过期时间需要管理员权限）
                    CheckUtil.checkStrNotBlank(param.getAdminPassword(), new CommonException("请输入管理员密码"));
                    CheckUtil.checkTrue(clipboard.getAdminPassword().equals(param.getAdminPassword()),
                            new CommonException("管理员密码错误"));

                    clipboard.setExpireTime(param.getExpireTime());
                    clipboard.setUpdatedTime(now);

                    return webClipboardRepository.save(clipboard);
                })
                .map(this::convertToVO);
    }

    @Override
    public Mono<Boolean> deleteClipboard(DeleteClipboardParam param) {
        CheckUtil.checkStrNotBlank(param.getShareId(), new CommonException("分享ID不能为空"));

        return webClipboardRepository.findByShareId(param.getShareId())
                .switchIfEmpty(Mono.error(new CommonException("剪贴板不存在")))
                .flatMap(clipboard -> {
                    // 验证管理员密码（删除需要管理员权限）
                    CheckUtil.checkStrNotBlank(param.getAdminPassword(), new CommonException("请输入管理员密码"));
                    CheckUtil.checkTrue(clipboard.getAdminPassword().equals(param.getAdminPassword()),
                            new CommonException("管理员密码错误"));

                    return webClipboardRepository.delete(clipboard);
                })
                .then(Mono.just(true))
                .onErrorReturn(false);
    }

    @Override
    public Mono<Integer> cleanExpiredClipboards() {
        return webClipboardRepository.deleteExpiredClipboards(LocalDateTime.now())
                .doOnSuccess(count -> {
                    if (count > 0) {
                        log.info("清理过期剪贴板: {} 个", count);
                    }
                });
    }

    @Override
    public Mono<String> generateUniqueShareId() {
        return Mono.defer(() -> {
                    String shareId = RandomUtil.randomString(SHARE_ID_CHARS, SHARE_ID_LENGTH);
                    return webClipboardRepository.existsByShareId(shareId)
                            .flatMap(exists -> {
                                if (exists) {
                                    // 如果存在，递归重试
                                    return generateUniqueShareId();
                                }
                                return Mono.just(shareId);
                            });
                })
                .retry(MAX_RETRY_COUNT)
                .onErrorMap(e -> new CommonException("生成唯一分享ID失败，请稍后重试"));
    }

    @Override
    public String generateDefaultPassword() {
        return RandomUtil.randomNumbers(PASSWORD_LENGTH);
    }

    @Override
    public Mono<Boolean> checkPasswordFreeAccess(CheckPasswordFreeAccessParam param) {
        return webClipboardRepository.findByShareId(param.getShareId())
                .switchIfEmpty(Mono.error(new CommonException("剪贴板不存在或已过期")))
                .flatMap(clipboard -> {
                    // 检查剪贴板是否过期
                    if (clipboard.getExpireTime().isBefore(LocalDateTime.now())) {
                        return Mono.error(new CommonException("剪贴板已过期"));
                    }

                    // 检查是否允许无密码访问（即访问密码为空或空字符串）
                    boolean passwordFree = StrUtil.isBlank(clipboard.getPassword());
                    return Mono.just(passwordFree);
                })
                .doOnError(e -> log.error("检查无密码访问失败: shareId={}, error={}", param.getShareId(), e.getMessage()));
    }

    /**
     * 生成管理员密码
     */
    private String generateAdminPassword() {
        return RandomUtil.randomString(SHARE_ID_CHARS, ADMIN_PASSWORD_LENGTH);
    }


    /**
     * 转换为VO对象（默认非管理员访问）
     */
    private WebClipboardVO convertToVO(WebClipboard clipboard) {
        return convertToVO(clipboard, false);
    }

    /**
     * 转换为VO对象
     */
    private WebClipboardVO convertToVO(WebClipboard clipboard, boolean isAdminAccess) {
        WebClipboardVO vo = new WebClipboardVO();
        BeanUtils.copyProperties(clipboard, vo);

        // 设置是否有密码保护
        vo.setHasPassword(StrUtil.isNotBlank(clipboard.getPassword()));

        // 设置是否为管理员访问
        vo.setIsAdmin(isAdminAccess);

        // 访问密码始终返回（供用户知晓）
        vo.setAccessPassword(clipboard.getPassword());

        // 管理员密码仅在管理员访问时返回
        if (!isAdminAccess) {
            vo.setAdminPassword(null);
        }

        // 计算剩余有效时间
        LocalDateTime now = LocalDateTime.now();
        if (clipboard.getExpireTime().isAfter(now)) {
            Duration duration = Duration.between(now, clipboard.getExpireTime());
            vo.setRemainingHours(duration.toHours());
            vo.setIsExpired(false);
        } else {
            vo.setRemainingHours(0L);
            vo.setIsExpired(true);
        }

        return vo;
    }
}
